/*
 * Copyright (C) 2010-2014 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package trs.wmata.api;

import android.content.Context;

import android.text.TextUtils;

import app.transittool.R;

import app.transittool.model.Agency;
import app.transittool.model.DataRetrievalException;
import app.transittool.model.InconceivableException;

import app.transittool.util.Log;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.util.Date;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;

/*
 * http://developer.wmata.com/docs/Home
 */
public final class Api {
    private static final String TAG = "Api";
    private final Context context;
    private final Agency agency;
    private final String apiBase;
    private final String apiKey;

    public Api(final Context context, final Agency agency, final String apiBase, final String apiKey) {
        this.context = context;
        this.agency = agency;
        this.apiBase = apiBase;
        this.apiKey = apiKey;
    }

    private static final String DATE_FORMAT = "yyyy-MM-dd";

    /**
     * Returns train arrival information as it appears on the Public Information
     * Displays throughout the system.
     */
    private static final String API_M05_TRAIN_PREDICTION = "{0}/StationPrediction.svc/GetPrediction/{2}?api_key={1}&subscription-key={1}";

    /** Returns entrances to Metro stations. */
    private static final String API_M08_TRAIN_STATION_ENTRANCES = "{0}/Rail.svc/StationEntrances?lat={2}&lon={3}&radius={4,number,#}&api_key={1}&subscription-key={1}";

    /** Returns the list of all bus stops. */
    private static final String API_M10_BUS_STOPS = "{0}/Bus.svc/Stops?lat={2}&lon={3}&radius={4,number,#}&api_key={1}&subscription-key={1}";

    /** Returns the bus route details for a specific route. */
    private static final String API_M12_BUS_ROUTE_DETAILS = "{0}/Bus.svc/RouteDetails?routeId={2}&date={3}&api_key={1}&subscription-key={1}";

    /** Returns the bus schedule for a specific bus stop. */
    private static final String API_M14_BUS_STOP_SCHEDULE = "{0}/Bus.svc/StopSchedule?stopId={2}&date={3}&api_key={1}&subscription-key={1}";

    /** Returns list of all stations in the system. */
    private static final String API_GET_RAIL_STATIONS = "{0}/Rail.svc/Stations?api_key={1}&subscription-key={1}";

    /**
     * Returns the bus arrival predictions for a specific bus stop according to
     * the real-time positions of the buses.
     */
    private static final String API_M15_BUS_PREDICTION = "{0}/NextBusService.svc/Predictions?StopID={2}&api_key={1}&subscription-key={1}";

    private void sendRequest(final ApiResponse handler, final String urlTemplate, final Object... arguments) throws DataRetrievalException {
        sendRequest(false, null, handler, urlTemplate, arguments);
    }

    private void sendRequest(final boolean cacheRespData, final String cacheKey, final ApiResponse handler, final String urlTemplate, final Object... arguments) throws DataRetrievalException {
        final long startTime = System.currentTimeMillis();

        if (System.getProperty("org.xml.sax.driver") == null) {
            System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
        }

        final String urlStr = MessageFormat.format(urlTemplate, arguments);
        final ApiSource apiSource;

        if (cacheRespData) {
            apiSource = new CachedApiSource(urlStr, cacheKey);
        } else {
            apiSource = new ApiSource(urlStr);
        }

        try {
            final SAXParserFactory factory = SAXParserFactory.newInstance();
            final SAXParser parser = factory.newSAXParser();
            parser.parse(apiSource.getInputSource(), handler);
            handler.setResponseCode(apiSource.getResponseCode());
            handler.setResponseMessage(apiSource.getResponseMessage());
        } catch (final ParserConfigurationException e) {
            throw new InconceivableException(context, R.string.parser_configuration_exception, e);
        } catch (final SocketTimeoutException e) {
            throw new DataRetrievalException(context, R.string.socket_timeout_exception, e);
        } catch (final IOException e) {
            throw new DataRetrievalException(context, R.string.io_exception, e);
        } catch (final SAXException e) {
            throw new DataRetrievalException(context, R.string.sax_exception, e);
        } finally {
            apiSource.disconnect();

            // if(Log.isDebugEnabled(TAG)) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Log.d(TAG, "Elapsed time," + elapsedTime + "," + urlStr);

            // }
        }
    }

    public NextTrain getNextTrain(String codeStr, final String altCode) throws DataRetrievalException {
        assertNotEmpty("codeStr", codeStr);

        if ((altCode != null) && (altCode.length() > 0)) {
            codeStr += ("," + altCode);
        }

        final NextTrain response = new NextTrain(agency);
        sendRequest(response, API_M05_TRAIN_PREDICTION, apiBase, apiKey, codeStr);

        return response;
    }

    public TrainStationEntrances getNearbyTrainStationEntrances(final double lat, final double lon, final int radius) throws DataRetrievalException {
        final TrainStationEntrances response = new TrainStationEntrances(agency);
        sendRequest(response, API_M08_TRAIN_STATION_ENTRANCES, apiBase, apiKey, lat, lon, radius);

        return response;
    }

    public BusStopSchedule getBusStopSchedule(final String stopId) throws DataRetrievalException {
        assertNotEmpty("stopId", stopId);

        final CharSequence dateStr = android.text.format.DateFormat.format(DATE_FORMAT, new Date());

        final BusStopSchedule response = new BusStopSchedule(context);
        sendRequest(true, null, response, API_M14_BUS_STOP_SCHEDULE, apiBase, apiKey, stopId, dateStr);

        return response;
    }

    public NextBus getNextBus(final String stopId) throws DataRetrievalException {
        assertNotEmpty("stopId", stopId);

        final NextBus response = new NextBus(agency);
        sendRequest(response, API_M15_BUS_PREDICTION, apiBase, apiKey, stopId);

        return response;
    }

    public BusStops getNearbyBusStops(final double lat, final double lon, final int radius) throws DataRetrievalException {
        final BusStops response = new BusStops(context);
        sendRequest(response, API_M10_BUS_STOPS, apiBase, apiKey, lat, lon, radius);

        return response;
    }

    public RouteDetails getRouteDetails(final String routeId) throws DataRetrievalException {
        assertNotEmpty("routeId", routeId);

        final CharSequence dateStr = android.text.format.DateFormat.format(DATE_FORMAT, new Date());

        final RouteDetails response = new RouteDetails();
        sendRequest(true, null, response, API_M12_BUS_ROUTE_DETAILS, apiBase, apiKey, routeId, dateStr);

        return response;
    }

    public RailStations getAllRailStations() throws DataRetrievalException {
        final CharSequence dateStr = android.text.format.DateFormat.format(DATE_FORMAT, new Date());

        final String cacheKey = "api://GetRailStations/all" + dateStr;

        final RailStations response = new RailStations(context);

        sendRequest(true, cacheKey, response, API_GET_RAIL_STATIONS, apiBase, apiKey);

        return response;
    }

    private static void assertNotEmpty(final String name, final String s) {
        if (TextUtils.isEmpty(s)) {
            throw new IllegalArgumentException(name + " must not be empty.");
        }
    }
}
